package com.swak.async.persistence.proxy;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;

import org.springframework.util.Assert;

import com.swak.asm.ClassGenerator;
import com.swak.asm.ClassGenerator.Dc;
import com.swak.async.persistence.BaseMapperImpl;
import com.swak.utils.ClassHelper;
import com.swak.utils.Lists;
import com.swak.utils.StringUtils;

/**
 * 代理工厂
 *
 * @author: lifeng
 * @date: 2020/3/29 20:32
 */
public class ProxyFactory {
	private static final AtomicLong PROXY_CLASS_COUNTER = new AtomicLong(0);
	private static final Map<ClassLoader, Map<Class<?>, Object>> PROXY_CACHE_MAP = new WeakHashMap<>();
	private static final Object PENDING_GENERATION_MARKER = new Object();

	/**
	 * 创建代理类
	 * 
	 * @param type
	 * @param template
	 * @return
	 */
	public static Object newProxy(Class<?> type) {
		return newProxy(ClassHelper.getClassLoader(ProxyFactory.class), type);
	}

	/**
	 * 创建代理类
	 * 
	 * @param type
	 * @param template
	 * @return
	 */
	public static Object newProxy(ClassLoader cl, Class<?> type) {
		Assert.isTrue(type != null, "type Can Not Be Null");
		Assert.isTrue(type.isInterface(), "type Can Not Be Null");

		// get cache by class loader.
		final Map<Class<?>, Object> cache;
		synchronized (PROXY_CACHE_MAP) {
			cache = PROXY_CACHE_MAP.computeIfAbsent(cl, k -> new HashMap<>());
		}
		Object proxy = null;
		synchronized (cache) {
			do {
				Object value = cache.get(type);
				if (value instanceof Reference<?>) {
					proxy = ((Reference<?>) value).get();
					if (proxy != null) {
						return proxy;
					}
				}

				if (value == PENDING_GENERATION_MARKER) {
					try {
						cache.wait();
					} catch (InterruptedException e) {
					}
				} else {
					cache.put(type, PENDING_GENERATION_MARKER);
					break;
				}
			} while (true);
		}

		List<String> generics = extractModelClass4Interface(type);
		String pk = type.getPackage().getName();
		long id = PROXY_CLASS_COUNTER.getAndIncrement();
		ClassGenerator ccp = null;
		try {
			String ccp_name = pk + ".Proxy" + id;
			ccp = ClassGenerator.newInstance(cl);
			ccp.setClassName(ccp_name);
			ccp.setSuperClass(BaseMapperImpl.class);
			ccp.addInterface(type);

			List<String> signatures = Lists.newArrayList();
			signatures.add("L" + BaseMapperImpl.class.getName() + "<L" + StringUtils.join(generics, ";L") + ";>");
			signatures.add("L" + type.getName());
			signatures.add("L" + Dc.class.getName());
			String signature = StringUtils.replace(StringUtils.join(signatures, ";"), ".", "/") + ";";
			ccp.setGenericSignature(signature);

			ccp.addConstructor(Modifier.PUBLIC, new Class<?>[] {}, new Class<?>[0], "super($1);");
			Class<?> clazz = ccp.toClass();
			proxy = clazz.getConstructor().newInstance();
		} catch (RuntimeException e) {
			throw e;
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage(), e);
		} finally {
			// release ClassGenerator
			if (ccp != null) {
				ccp.release();
			}
			synchronized (cache) {
				if (proxy == null) {
					cache.remove(type);
				} else {
					cache.put(type, new WeakReference<Object>(proxy));
				}
				cache.notifyAll();
			}
		}
		return proxy;
	}

	/**
	 * 提取泛型模型
	 */
	public static List<String> extractModelClass4Interface(Class<?> mapperClass) {
		List<String> generics = Lists.newArrayList();
		Type[] types = mapperClass.getGenericInterfaces();
		ParameterizedType target = null;
		for (Type type : types) {
			if (type instanceof ParameterizedType) {
				Type[] typeArray = ((ParameterizedType) type).getActualTypeArguments();
				if (typeArray != null && typeArray.length > 0) {
					for (Type t : typeArray) {
						if (t instanceof TypeVariable || t instanceof WildcardType) {
							break;
						} else {
							target = (ParameterizedType) type;
							break;
						}
					}
				}
				break;
			}
		}
		types = target.getActualTypeArguments();
		for (Type type : types) {
			generics.add(type.getTypeName());
		}
		return generics;
	}
}
